Skip to content

07 Docker 基础 - 数据卷挂载

容器是隔离环境,容器内程序的文件、配置、运行时产生的容器都在容器内部,我们要读写容器内的文件非常不方便。大家思考几个问题:

  • 如果要升级 MySQL 版本,需要销毁旧容器,那么数据岂不是跟着被销毁了?
  • MySQL、Nginx 容器运行后,如果我要修改其中的某些配置该怎么办?
  • 我想要让 Nginx 代理我的静态资源怎么办?

因此,容器提供程序的运行环境,但是程序运行产生的数据、程序运行依赖的配置都应该与容器解耦

什么是数据卷

数据卷(volume) 是一个虚拟目录,是 容器内目录宿主机目录 之间映射的桥梁。

官方解释

Volumes are the preferred mechanism for persisting data generated by and used by Docker containers. While bind mounts are dependent on the directory structure and OS of the host machine, volumes are completely managed by Docker. Volumes have several advantages over bind mounts: 卷是 Docker 容器生成和使用数据的首选持久化机制。绑定挂载依赖于主机的目录结构和操作系统,而卷则完全由 Docker 管理。与绑定挂载相比,卷有几个优点:

  • Volumes are easier to back up or migrate than bind mounts. 卷比绑定挂载更容易备份或迁移。
  • You can manage volumes using Docker CLI commands or the Docker API. 您可以使用 Docker CLI 命令或 Docker API 管理卷。
  • Volumes work on both Linux and Windows containers. 卷可在 Linux 和 Windows 容器上运行。
  • Volumes can be more safely shared among multiple containers. 多个容器可以更安全地共享卷。
  • Volume drivers let you store volumes on remote hosts or cloud providers, encrypt the contents of volumes, or add other functionality. 卷驱动程序可以让你在远程主机或云提供商上存储卷,加密卷的内容,或添加其他功能。
  • New volumes can have their content pre-populated by a container. 新卷的内容可由容器预先填充。
  • Volumes on Docker Desktop have much higher performance than bind mounts from Mac and Windows hosts. 与 Mac 和 Windows 主机上的绑定挂载相比,Docker Desktop 上的卷具有更高的性能。

In addition, volumes are often a better choice than persisting data in a container's writable layer, because a volume doesn't increase the size of the containers using it, and the volume's contents exist outside the lifecycle of a given container. 此外,与在容器的可写层中持久化数据相比,卷通常是更好的选择,因为卷不会增加使用它的容器的大小,而且卷的内容存在于给定容器的生命周期之外。

以 Nginx 为例,我们知道 Nginx 中有两个关键的目录:

  • html:放置一些静态资源
  • conf:放置配置文件

如果我们要让 Nginx 代理我们的静态资源,最好是放到 html 目录;如果我们要修改 Nginx 的配置,最好是找到 conf 下的 nginx.conf 文件。

但遗憾的是,容器运行的 Nginx 所有的文件都在容器内部。所以我们必须利用数据卷将两个目录与宿主机目录关联,方便我们操作。

docker-volume

在上图中:

  • 我们创建了两个数据卷:confhtml
  • Nginx 容器内部的 conf 目录和 html 目录分别与两个数据卷关联。
  • 而数据卷 conf 和 html 分别指向了宿主机的 /var/lib/docker/volumes/conf/_data 目录和 /var/lib/docker/volumes/html/_data 目录

这样以来,容器内的 confhtml 目录就 与宿主机的 confhtml 目录关联起来,我们称为挂载。此时,我们操作宿主机的 /var/lib/docker/volumes/html/_data 就是在操作容器内的 /usr/share/nginx/html/_data 目录。只要我们将静态资源放入宿主机对应目录,就可以被 Nginx 代理了。

TIP

  • /var/lib/docker/volumes 这个目录就是默认的存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为 /数据卷名/_data
  • 为什么不让容器目录直接指向宿主机目录呢
    • 因为直接指向宿主机目录就与宿主机强耦合了,如果切换了环境,宿主机目录就可能发生改变了。由于容器一旦创建,目录挂载就无法修改,这样容器就无法正常工作了。
    • 但是容器指向数据卷,一个逻辑名称,而数据卷再指向宿主机目录,就不存在强耦合。如果宿主机目录发生改变,只要改变数据卷与宿主机目录之间的映射关系即可。
  • 不过,我们通过由于数据卷目录比较深,不好寻找,通常我们也 允许让容器直接与宿主机目录挂载而不使用数据卷,具体参考 08.Docker 基础 - 本地目录挂载

数据卷命令

docker volume command

bash
 docker volume --help

Usage:  docker volume COMMAND

Manage volumes

Commands:
  create      Create a volume
  inspect     Display detailed information on one or more volumes
  ls          List volumes
  prune       Remove unused local volumes
  rm          Remove one or more volumes

Run 'docker volume COMMAND --help' for more information on a command.
bash
 tldr  docker volume

  Manage Docker volumes.
  More information: <https://docs.docker.com/engine/reference/commandline/volume/>.

  Create a volume:

      docker volume create volume_name

  Create a volume with a specific label:

      docker volume create --label label volume_name

  Create a `tmpfs` volume a size of 100 MiB and an uid of 1000:

      docker volume create --opt type=tmpfs --opt device=tmpfs --opt o=size=100m,uid=1000 volume_name

  List all volumes:

      docker volume ls

  Remove a volume:

      docker volume rm volume_name

  Display information about a volume:

      docker volume inspect volume_name

  Remove all unused local volumes:

      docker volume prune

  Display help for a subcommand:

      docker volume subcommand --help

常见命令

命令说明文档地址
docker volume create创建数据卷docker volume create
docker volume ls查看所有数据卷docker volume ls
docker volume rm删除指定数据卷docker volume rm
docker volume inspect查看某个数据卷的详情docker volume inspect
docker volume prune清除数据卷docker volume prune

注意

容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,是不能设置数据卷的。而且创建容器的过程中,数据卷会自动创建

volume 命令演示

命名加密卷

演示一下利用 Nginx 容器部署静态资源:nginx 的 html 命名数据卷。

需求

  • 创建 Nginx 容器,修改 nginx 容器内的 html 目录下的 index.html 文件内容
  • 将静态资源部署到 nginx 的 html 目录

提示

  • 在执行 docker run 命令时,使用 -v 数据卷:容器内目录 可以完成数据卷挂载
  • 当创建容器时,如果挂载了数据卷且数据卷不存在,会自动创建数据卷
  1. 首先创建容器并指定数据卷,注意通过 -v 参数来指定数据卷。

    bash
     docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
    Unable to find image 'nginx:latest' locally
    latest: Pulling from library/nginx
    578acb154839: Pull complete
    e398db710407: Pull complete
    85c41ebe6d66: Pull complete
    7170a263b582: Pull complete
    8f28d06e2e2e: Pull complete
    6f837de2f887: Pull complete
    c1dfc7e1671e: Pull complete
    Digest: sha256:86e53c4c16a6a276b204b0fd3a8143d86547c967dc8258b3d47c3a21bb68d3c6
    Status: Downloaded newer image for nginx:latest
    a415c4c37209f030752326a41ddc5485ed83b498087e6082f0121de3a3bbbb52
  2. 查看数据卷。

    bash
     docker volume ls
    DRIVER    VOLUME NAME
    local     0cdc74b1d00927c0e628ca8d6600316fc1c4c90b15eec972b75a8ac008661819
    local     html
  3. 查看数据卷详情。

    bash
     docker volume inspect html
    [
        {
            "CreatedAt": "2023-11-05T09:32:30+08:00",
            "Driver": "local",
            "Labels": null,
            "Mountpoint": "/var/lib/docker/volumes/html/_data",
            "Name": "html",
            "Options": null,
            "Scope": "local"
        }
    ]
  4. 查看 /var/lib/docker/volumes/html/_data 目录。

    bash
     la
    .rw-r--r-- ubuntu ubuntu 497B 2 weeks ago 50x.html
    .rw-r--r-- ubuntu ubuntu  19B an hour ago index.html
  5. 进入该目录,并随意修改 index.html 内容。

    bash
     curl http://150.158.99.53:80
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
    html { color-scheme: light dark; }
    body { width: 35em; margin: 0 auto;
    font-family: Tahoma, Verdana, Arial, sans-serif; }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
     vim index.html
  6. 打开页面查看效果。

    bash
     cat -n index.html
         1  Welcome to Docker!
    
     curl http://150.158.99.53:80
    Welcome to Docker!
  7. 进入容器内部,查看 /usr/share/nginx/html 目录内的文件是否变化。

    bash
     dex nginx cat /usr/share/nginx/html/index.html # dex is alias of 'docker exec -it'
    Welcome to Docker!

匿名加密卷

演示一下 MySQL 的匿名数据卷。

  1. 查看 MySQL 容器详细信息。

    bash
     dkin mysql # dkin is alias of 'docker inspect'
     ... 输出内容太多,省略
     dkin mysql | jq '.[0].Config.Volumes' # 使用 jq 查看容器配置的数据卷
    {
      "/var/lib/mysql": {}
    }
     dkin mysql | jq '.[0].Mounts' # 使用 jq 查看容器挂载的数据卷
    [
      {
        "Type": "volume",
        "Name": "9c23b8c7e4d34c0458f255c74c577a57cc97697f9b29619324458cbd9bd417d6",
        "Source": "/var/lib/docker/volumes/9c23b8c7e4d34c0458f255c74c577a57cc97697f9b29619324458cbd9bd417d6/_data",
        "Destination": "/var/lib/mysql",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
      }
    ]
  2. 使用 jq 查看容器配置的数据卷。dkin mysql | jq '.[0].Config.Volumes

    json
    {
      "/var/lib/mysql": {}
    }

    可以发现这个容器声明了一个本地目录,需要挂载数据卷,但是数据卷未定义。这就是匿名卷。

  3. 使用 jq 查看容器挂载的数据卷。dkin mysql | jq '.[0].Mounts'

    json
    [
      {
        "Type": "volume",
        "Name": "9c23b8c7e4d34c0458f255c74c577a57cc97697f9b29619324458cbd9bd417d6",
        "Source": "/var/lib/docker/volumes/9c23b8c7e4d34c0458f255c74c577a57cc97697f9b29619324458cbd9bd417d6/_data",
        "Destination": "/var/lib/mysql",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
      }
    ]

    可以发现,其中有几个关键属性:

    • Name:数据卷名称。由于定义容器未设置容器名,这里的就是匿名卷自动生成的名字,一串 hash 值。
    • Source:宿主机目录
    • Destination : 容器内的目录

    上述配置是将容器内的 /var/lib/mysql 这个目录,与数据卷 9c23b8c7e4d34c0458f255c74c577a57cc97697f9b29619324458cbd9bd417d6 挂载。于是在宿主机中就有了 /var/lib/docker/volumes/9c23b8c7e4d34c0458f255c74c577a57cc97697f9b29619324458cbd9bd417d6/_data 这个目录。这就是匿名数据卷对应的目录,其使用方式与普通数据卷没有差别。

  4. 查看该目录下的 MySQL 的 data 文件。

    bash
     lsa /var/lib/docker/volumes/9c23b8c7e4d34c0458f255c74c577a57cc97697f9b29619324458cbd9bd417d6/_data
    total 99M
    drwxrwxrwt 7 lxd  ubuntu 4.0K Nov  5 11:00  .
    drwx-----x 3 root root   4.0K Nov  5 10:59  ..
    -rw-r----- 1 lxd  docker   56 Nov  5 10:59  auto.cnf
    -rw-r----- 1 lxd  docker 2.9M Nov  5 11:00  binlog.000001
    -rw-r----- 1 lxd  docker  157 Nov  5 11:00  binlog.000002
    -rw-r----- 1 lxd  docker   32 Nov  5 11:00  binlog.index
    -rw------- 1 lxd  docker 1.7K Nov  5 11:00  ca-key.pem
    -rw-r--r-- 1 lxd  docker 1.1K Nov  5 11:00  ca.pem
    -rw-r--r-- 1 lxd  docker 1.1K Nov  5 11:00  client-cert.pem
    -rw------- 1 lxd  docker 1.7K Nov  5 11:00  client-key.pem
    -rw-r----- 1 lxd  docker 192K Nov  5 11:02 '#ib_16384_0.dblwr'
    -rw-r----- 1 lxd  docker 8.2M Nov  5 10:59 '#ib_16384_1.dblwr'
    -rw-r----- 1 lxd  docker 5.6K Nov  5 11:00  ib_buffer_pool
    -rw-r----- 1 lxd  docker  12M Nov  5 11:00  ibdata1
    -rw-r----- 1 lxd  docker  12M Nov  5 11:00  ibtmp1
    drwxr-x--- 2 lxd  docker 4.0K Nov  5 11:00 '#innodb_redo'
    drwxr-x--- 2 lxd  docker 4.0K Nov  5 11:00 '#innodb_temp'
    drwxr-x--- 2 lxd  docker 4.0K Nov  5 11:00  mysql
    -rw-r----- 1 lxd  docker  31M Nov  5 11:00  mysql.ibd
    lrwxrwxrwx 1 lxd  docker   27 Nov  5 11:00  mysql.sock -> /var/run/mysqld/mysqld.sock
    drwxr-x--- 2 lxd  docker 4.0K Nov  5 11:00  performance_schema
    -rw------- 1 lxd  docker 1.7K Nov  5 11:00  private_key.pem
    -rw-r--r-- 1 lxd  docker  452 Nov  5 11:00  public_key.pem
    -rw-r--r-- 1 lxd  docker 1.1K Nov  5 11:00  server-cert.pem
    -rw------- 1 lxd  docker 1.7K Nov  5 11:00  server-key.pem
    drwxr-x--- 2 lxd  docker 4.0K Nov  5 11:00  sys
    -rw-r----- 1 lxd  docker  16M Nov  5 11:02  undo_001
    -rw-r----- 1 lxd  docker  16M Nov  5 11:02  undo_002

注意

每一个不同的镜像,将来创建容器后内部有哪些目录可以挂载,可以参考 DockerHub 对应镜像的页面。

总结

  • 什么是数据卷?
    • 数据卷是一个虚拟目录,它将宿主机目录映射到容器内目录,方便我们操作容器内文件,或者方便迁移容器产生的数据
  • 如何挂载数据卷?
    • 在创建容器时,利用 -v 数据卷名:容器内目录 完成挂载
    • 容器创建时,如果发现挂载的数据卷不存在时,会自动创建
  • 数据卷的常见命令有哪些?
    • docker volume ls: 查看数据卷
    • docker volume rm: 删除数据卷
    • docker volume inspect: 查看数据卷详情
    • docker volume prune: 删除未使用的数据卷